/*******************************************************************************
 * Copyright (c) 2003 Ferenc Hechler - ferenc_hechler@users.sourceforge.net
 * 
 * This file is part of the Raw SOAP message sender plugin for Eclipse.
 *
 * The Raw SOAP message sender plugin for Eclipseis free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or (at your option) any later version.
 * 
 * The Raw SOAP message sender plugin for Eclipse is distributed
 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with the Raw SOAP message sender plugin for Eclipse;
 * if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *  
 *******************************************************************************/
package net.sf.wscep.widgets;

import java.util.Stack;

import org.eclipse.swt.custom.ExtendedModifyEvent;
import org.eclipse.swt.custom.ExtendedModifyListener;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Composite;

/**
 * adds undo/redo functionality to swt.StyledText
 * 
 * @author Ferenc Hechler
 * @version Revision 1.0, Date: 2003/10/24
 * 
 * Revision 1.0
 * Initial Revision
 * 
 */
public class UndoableStyledText extends StyledText {

        class EditInformation {
                int start;
                int length;
                String replaceText;
                public EditInformation(int start, int length, String replaceText) {
                        this.start = start;
                        this.length = length;
                        this.replaceText = replaceText;
                }
                /**
                 * apply the edit info
                 * @param styledText will be modified
                 * @return undo info to reverse the edit done
                 */
                EditInformation editStyledText(StyledText styledText) {
                        String text = styledText.getText();
                        String undoText = text.substring(start, start + length);
                        String editedText = text.substring(0, start) + replaceText + text.substring(start + length);
                        styledText.setText(editedText);
                        styledText.setSelection(start, start + replaceText.length());
                        EditInformation undoInfo = new EditInformation(start, replaceText.length(), undoText);                     
                        return undoInfo;
                }
                /**
                 * merges subsequent deletes and inserts to one EditInfo
                 * @return true if merged successfully
                 */
                boolean merge(int mStart, int mLength, String mReplaceText) {
                        boolean merged = false;
                        if (mReplaceText.equals("") && replaceText.equals("") && (mLength == 1) && (mStart == start + length)) {
                                merged = true;
                                length += mLength;
                        } 
                        else if ((mLength == 0) && (length == 0) && (mReplaceText.length() == 1) && (mStart == start - 1)) {
                                merged = true;
                                start = mStart;
                                replaceText = mReplaceText + replaceText;
                        }
                        else if ((mLength == 0) && (length == 0) && (mReplaceText.length() == 1) && (mStart == start)) {
                                merged = true;
                                start = mStart;
                                replaceText += mReplaceText;
                        }
                        return merged;
                }
        }
        
        class UndoController {
                StyledText controlledText = null;
                int maxUndoes = 0;
                boolean selfmodifying = false;
                Stack undoStack = new Stack();
                Stack redoStack = new Stack();
                protected UndoController(StyledText styledText, int maxUndoes) {
                        controlledText = styledText;
                        this.maxUndoes = maxUndoes;
                }
                void rememberEdit(int start, int length, String replacedText) {
                        if (!selfmodifying) {
                                if (!redoStack.isEmpty())
                                        redoStack.clear();
                                boolean mergedToPreviousEdit = false;
                                if (!undoStack.isEmpty()) {
                                        EditInformation undoEditInfo = (EditInformation) undoStack.peek();
                                        mergedToPreviousEdit = undoEditInfo.merge(start, length, replacedText);
                                }
                                if (!mergedToPreviousEdit) {
                                        EditInformation undoEditInfo = new EditInformation(start, length, replacedText);
                                        undoStack.push(undoEditInfo);
                                        if ((maxUndoes >= 0) && (undoStack.size() > maxUndoes))
                                                undoStack.removeElementAt(0);
                                }
                        }
                }
                void undo() {
                        if (!undoStack.isEmpty()) {
                                selfmodifying = true;
                                EditInformation undoEditInfo = (EditInformation) undoStack.pop();
                                EditInformation redoEditInfo = undoEditInfo.editStyledText(controlledText);
                                redoStack.push(redoEditInfo);
                                selfmodifying = false;
                        }
                }
                void redo() {
                        if (!redoStack.isEmpty()) {
                                selfmodifying = true;
                                EditInformation redoEditInfo = (EditInformation) redoStack.pop();
                                EditInformation undoEditInfo = redoEditInfo.editStyledText(controlledText);
                                undoStack.push(undoEditInfo);
                                selfmodifying = false;
                        }
                }
                void clearUndoStack() {
                        undoStack.clear();
                        redoStack.clear();
                }
        }

        UndoController undoController = null;
        
        public UndoableStyledText(Composite parent, int options, int maxUndoes) {
                super(parent, options);
                
                undoController = new UndoController(this, maxUndoes);
                ExtendedModifyListener undoListener = new ExtendedModifyListener() {
                        public void modifyText(ExtendedModifyEvent e) {
                                undoController.rememberEdit(e.start, e.length, e.replacedText);
                        }
                };
                addExtendedModifyListener(undoListener);
        }
        
        public void undo() {
                undoController.undo();
        }
        public void redo() {
                undoController.redo();
        }
        public void clearUndoStack() {
                undoController.clearUndoStack();
        }
        

}
